package model;

import Enums.Notifier;
import java.awt.image.BufferedImage;
import controller.Controller_Fitness;
import java.io.Serializable;
import java.util.Observable;
import org.jgap.Genotype;
import org.jgap.InvalidConfigurationException;
import org.jgap.event.EventManager;
import org.jgap.impl.ChromosomePool;
import org.jgap.impl.GABreeder;
import org.jgap.impl.MutationOperator;
import org.jgap.impl.StockRandomGenerator;
import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.DeltaFitnessEvaluator;
import org.jgap.FitnessFunction;
import org.jgap.impl.CompositeGene;
import org.jgap.impl.IntegerGene;
import org.jgap.impl.TournamentSelector;

/**
 * ModelKlasse fuer das Strichbild. Kapselt auch alle evolutionsspezifischen Daten
 * wie die aktuelle Population und die Konfiguration der Evolution
 */
public class Model_Image_Line extends Observable implements Serializable {

    private Model_Config config;
    private Model_Image_Original image_Original;
    private Genotype genotype;
    private Configuration genoConf;
    private transient BufferedImage fittestImage;
    private transient BufferedImage actualImage;
    private int[][] grayValues;

    /**
     * Konstruktor
     * @param model_config
     * @param model_image_original
     */
    public Model_Image_Line(Model_Config model_config, Model_Image_Original model_image_original) {
        this.config = model_config;
        this.image_Original = model_image_original;
    }

    /**
     * Erzeugt ein neues Konfigurationsobjekt (org.jgap.Configuration) oder resettet
     * das bereits bestehende, das fuer die Evolution genutzt wird. Das Konfigurations-
     * objekt enthaelt alle wichtigen Einstellungen wie Selektoren, genetische Operatoren
     * Populationsgroesse etc.
     * @throws InvalidConfigurationException
     */
    public void setupEvolution() throws InvalidConfigurationException {
        if (genoConf == null) {
            genoConf = new Configuration("config", "config");
        } else {
            Configuration.reset("config");
            genoConf = new Configuration("config", "config");
        }

        genoConf.setBreeder(new GABreeder());
        genoConf.setRandomGenerator(new StockRandomGenerator());
        genoConf.setEventManager(new EventManager());
        genoConf.setMinimumPopSizePercent(0);
        genoConf.setSelectFromPrevGen(1.0d);
        genoConf.setKeepPopulationSizeConstant(true);
        genoConf.setFitnessEvaluator(new DeltaFitnessEvaluator()); //kleinerer Fitnesswert bedeutet groessere Fitness
        genoConf.setChromosomePool(new ChromosomePool());

        TournamentSelector tournamentSelector = new TournamentSelector(genoConf, 5, 0.6d);
        genoConf.addNaturalSelector(tournamentSelector, false);
        genoConf.addGeneticOperator(new MutationOperator(genoConf));
        FitnessFunction fitFunc = new Controller_Fitness(config, image_Original, this);
        genoConf.setFitnessFunction(fitFunc);

        if (genotype == null) { //erste Erzeugung
            genoConf.setPopulationSize(config.getImagesPerGeneration());

            CompositeGene[] sampleGenes = new CompositeGene[config.getLinesPerImage()];
            for (int i = 0; i < sampleGenes.length; i++) {
                sampleGenes[i] = new CompositeGene(genoConf);
                sampleGenes[i].addGene(new IntegerGene(genoConf, 0, getWidth()));   //x1
                sampleGenes[i].addGene(new IntegerGene(genoConf, 0, getHeight()));   //y1
                sampleGenes[i].addGene(new IntegerGene(genoConf, 0, getWidth()));   //x2
                sampleGenes[i].addGene(new IntegerGene(genoConf, 0, getHeight()));   //y2
                sampleGenes[i].addGene(new IntegerGene(genoConf, 1, 3));   //breite
                sampleGenes[i].addGene(new IntegerGene(genoConf, 0, 100)); //composite
            }

            Chromosome sampleChromosome = new Chromosome(genoConf, sampleGenes);
            genoConf.setSampleChromosome(sampleChromosome);

            genotype = Genotype.randomInitialGenotype(genoConf);
        }
    }

    /**
     * setzt den genotype auf null, sodass eine komplett neue Evolution gestartet wird
     * @throws InvalidConfigurationException
     */
    public void newEvolution() throws InvalidConfigurationException {
        this.genotype = null;
        setupEvolution();
    }

    /**
     * Aendert die Anzahl der Striche pro Bild und die Anzahl der Bilder pro Generation dynamisch,
     * d.h. bei laufendem Evolutionsprozess uebernimmt dabei bereits bestehende Striche/Bilder.
     * @param configType die zu aendernde Einstellung (Notifier.Lines oder Notifier.GenerationSize)
     * @throws InvalidConfigurationException
     */
    /*
    public void configChanged(Notifier configType) throws InvalidConfigurationException {
        if (configType == Notifier.Lines) { //Striche pro Bild geaendert
            setupEvolution();
            Population population = genotype.getPopulation();
            CompositeGene[] moreOrLessGenes;
            int i;

            genoConf.setPopulationSize(config.getImagesPerGeneration());

            for (IChromosome chromosome : population.getChromosomes()) {    //gehe alle Chromosome durch
                moreOrLessGenes = new CompositeGene[config.getLinesPerImage()];

                for (i = 0; (i < chromosome.size()) && (i < config.getLinesPerImage()); i++) { //behalte die alten Gene
                    moreOrLessGenes[i] = (CompositeGene) chromosome.getGene(i);
                }
                for (; i < moreOrLessGenes.length; i++) {   //Fuege evtl. neue Gene hinzu
                    moreOrLessGenes[i] = new CompositeGene(genoConf);
                    moreOrLessGenes[i].addGene(new IntegerGene(genoConf, 0, getWidth()));   //x1
                    moreOrLessGenes[i].addGene(new IntegerGene(genoConf, 0, getHeight()));   //y1
                    moreOrLessGenes[i].addGene(new IntegerGene(genoConf, 0, getWidth()));   //x2
                    moreOrLessGenes[i].addGene(new IntegerGene(genoConf, 0, getHeight()));   //y2
                    moreOrLessGenes[i].addGene(new IntegerGene(genoConf, 1, 3));   //breite
                    moreOrLessGenes[i].addGene(new IntegerGene(genoConf, 30, 100)); //composite
                    for (Gene gen : moreOrLessGenes[i].getGenes()) {
                        gen.setAllele(new Integer(genoConf.getRandomGenerator().nextInt()));
                    }
                }
                chromosome.setGenes(moreOrLessGenes);
            }

            genoConf.setSampleChromosome(population.getChromosome(0));

        } else if (configType == Notifier.GenerationSize) { //Generationengroesse wurde geaendert
            Population oldPop = (Population) genotype.getPopulation().clone();
            newEvolution();
            //fuege die alte Populaton zur neuen hinzu (evtl. wird die neue komplett ersetzt)
            for (int i = 0; (i < oldPop.size()) && (i < genotype.getPopulation().size()); i++) {
                genotype.getPopulation().setChromosome(i, oldPop.getChromosome(i));
            }
        }
    }
*/
    /**
     * laesst den Genotyp einen Evolutionsschritt machen, d.h. eine Generation wird
     * entwickelt.
     */
    public void doEvolutionStep() {
        genotype.evolve();
    }

    /**
     * fuellt das zweidimensionale Array grayvalues mit Inhalt, d.h. mit dem
     * Grauwert des Strichbildes bei dem entsprechenden Pixel
     */
    private void create_grayValuematrix() {
        grayValues = new int[this.getWidth()][this.getHeight()];

        for (int i = 0; i < getWidth(); i++) {
            for (int j = 0; j < getHeight(); j++) {
                grayValues[i][j] = getGray(actualImage.getRGB(i, j));
            }
        }
    }

    /**
     * gibt den Grauwert eines rgb-Pixels zurueck
     * @param rgb
     * @return
     */
    private int getGray(int rgb) {
        return rgb & 0xFF;
    }

    /**
     * gibt das Grau-Werte-Array des Strichbildes zurueck
     * @return
     */
    public int[][] getGrayValues() {
        create_grayValuematrix();
        return this.grayValues;
    }

    /**
     *
     * @param width - gewuenschte Breite
     * @param height - gewuenschte Hoehe
     * @param hints - java.awt.Image Scale Type
     * @return skalierte Referenz von fittestImage
     */
    public java.awt.Image getResizedImage(int width, int height, int hints) {
        java.awt.Image tmp = fittestImage;
        if (tmp.getWidth(null) > width) {
            tmp = fittestImage.getScaledInstance(width, -1, hints);
        }
        if (tmp.getHeight(null) > height) {
            tmp = fittestImage.getScaledInstance(-1, height, hints);
        }
        return tmp;
    }

    /**
     * genotype und genoConf neu setzen
     * wobei genoConf von das eingegebene genotype entnommen wird
     * @param genotype
     */
    public void setGenotype(Genotype genotype) {
        this.genotype = genotype;
        this.genoConf = genotype.getConfiguration();
    }

    public Configuration getGenoConf() {
        return this.genoConf;
    }

    public Genotype getGenotype() {
        return genotype;
    }

    public BufferedImage getFittestImage() {
        return fittestImage;
    }

    public void setFittestImage(BufferedImage fittestImage) {
        this.fittestImage = fittestImage;
        setChanged();
        notifyObservers(Notifier.LineImage);
    }

    public BufferedImage getActualImage() {
        return actualImage;
    }

    public void setActualImage(BufferedImage actualImage) {
        this.actualImage = actualImage;
    }

    public int getWidth() {
        return image_Original.getWidth();
    }

    public int getHeight() {
        return image_Original.getHeight();
    }
}